﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Reflection;

namespace Postnuclear
  {
  /// <summary>
  /// Extension methods for <see cref="EventHandler"/>, <see cref="EventHandler{EventArgs}"/> and more specific event handlers.
  /// </summary>  
  public static partial class ExtensionMethods
    {
    #region Public

    // Methods

    /// <summary>
    /// Raises the specified event with empty <see cref="EventArgs.Empty"/> arguments.
    /// </summary>
    /// <typeparam name="TSender">The type of the sender object.</typeparam>
    /// <param name="handler">The event handler.</param>
    /// <param name="sender">The event sender.</param>
    [DebuggerHidden]
    public static void Raise<TSender>(this EventHandler handler, TSender sender)
      {
      Contract.Requires<ArgumentNullException>(sender != null);

      handler.Raise(sender, EventArgs.Empty);
      }

    /// <summary>
    /// Raises the specified event with specified arguments.
    /// </summary>
    /// <typeparam name="TSender">The type of the sender object.</typeparam>
    /// <param name="handler">The event handler.</param>
    /// <param name="sender">The event sender.</param>
    /// <param name="arguments">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    [DebuggerHidden]
    public static void Raise<TSender>(this EventHandler handler, TSender sender, EventArgs arguments)
      {
      Contract.Requires<ArgumentNullException>(sender != null);
      Contract.Requires<ArgumentNullException>(arguments != null);

      if (handler != null)
        {
        handler(sender, arguments);
        }
      }

    /// <summary>
    /// Raises the specified event with empty <see cref="EventArgs.Empty"/> arguments.
    /// </summary>
    /// <typeparam name="TSender">The type of the sender object.</typeparam>
    /// <param name="handler">The event handler.</param>
    /// <param name="sender">The event sender.</param>
    [DebuggerHidden]
    public static void Raise<TSender>(this EventHandler<EventArgs> handler, TSender sender)
      {
      Contract.Requires<ArgumentNullException>(sender != null);

      handler.Raise(sender, EventArgs.Empty);
      }

    /// <summary>
    /// Raises the specified event with specified arguments.
    /// </summary>
    /// <typeparam name="TSender">The type of the sender object.</typeparam>
    /// <typeparam name="TEventArgs">The type of the arguments.</typeparam>
    /// <param name="handler">The event handler.</param>
    /// <param name="sender">The event sender.</param>
    /// <param name="arguments">The arguments.</param>
    [DebuggerHidden]
    public static void Raise<TSender, TEventArgs>(this EventHandler<TEventArgs> handler, TSender sender, TEventArgs arguments)
      where TEventArgs : EventArgs
      {
      Contract.Requires<ArgumentNullException>(sender != null);
      Contract.Requires<ArgumentNullException>(arguments != null);

      if (handler != null)
        {
        handler(sender, arguments);
        }
      }

    /// <summary>
    /// Raises the specified event with specified value.
    /// </summary>
    /// <typeparam name="TSender">The type of the sender object.</typeparam>
    /// <typeparam name="TValue">The type of the value.</typeparam>
    /// <param name="handler">The event handler.</param>
    /// <param name="sender">The event sender.</param>
    /// <param name="value">The value.</param>
    [DebuggerHidden]
    public static void Raise<TSender, TValue>(this EventHandler<ValueEventArgs<TValue>> handler, TSender sender, TValue value)
      {
      Contract.Requires<ArgumentNullException>(sender != null);

      handler.Raise(sender, new ValueEventArgs<TValue>(value));
      }

    /// <summary>
    /// Raises the specified event with specified property name.
    /// </summary>
    /// <typeparam name="TSender">The type of the sender object.</typeparam>
    /// <param name="handler">The event handler.</param>
    /// <param name="sender">The event sender.</param>
    /// <param name="propertyName">Name of the property.</param>
    [DebuggerHidden]
    public static void Raise<TSender>(this PropertyChangedEventHandler handler, TSender sender, string propertyName)
      {
      Contract.Requires<ArgumentNullException>(sender != null);
      Contract.Requires<ArgumentNullException>(propertyName.HasSense());

      if (handler != null)
        {
        handler(sender, new PropertyChangedEventArgs(propertyName));
        }
      }

    /// <summary>
    /// Makes the weak event handler from strongly referenced one.
    /// </summary>
    /// <typeparam name="TEventArgs">The type of the arguments.</typeparam>
    /// <param name="handler">The event handler.</param>
    /// <param name="unregisterHandler">The unregister handler which is called when target handler target is garbage collected.</param>
    /// <returns>Weakened event handler.</returns>
    [DebuggerHidden]
    public static EventHandler<TEventArgs> MakeWeak<TEventArgs>(this EventHandler<TEventArgs> handler, Action<EventHandler<TEventArgs>> unregisterHandler)
      where TEventArgs : EventArgs
      {
      Contract.Requires<ArgumentNullException>(handler != null);
      Contract.Requires<ArgumentNullException>(unregisterHandler != null);

      Contract.Requires<ArgumentException>(!handler.Method.IsStatic, "Only instance methods are supported.");
      Contract.Requires<ArgumentException>(handler.Target != null, "Only instance methods are supported.");

      Contract.Ensures(Contract.Result<EventHandler<TEventArgs>>() != null);

      MethodInfo method = MakeWeakGenericDefinition.MakeGenericMethod(new Type[] { handler.Target.GetType(), typeof(TEventArgs) });
      return (EventHandler<TEventArgs>)method.Invoke(null, new object[] { handler, unregisterHandler });
      }

    #endregion

    #region Private

    private static readonly MethodInfo MakeWeakGenericDefinition
      = new Func<EventHandler<EventArgs>, Action<EventHandler<EventArgs>>, EventHandler<EventArgs>>(
        MakeWeak<object, EventArgs>).Method.GetGenericMethodDefinition();

    [DebuggerHidden]
    private static EventHandler<TEventArgs> MakeWeak<TTarget, TEventArgs>(this EventHandler<TEventArgs> handler, Action<EventHandler<TEventArgs>> unregisterHandler)
      where TTarget : class
      where TEventArgs : EventArgs
      {
      return new WeakEventHandler<TTarget, TEventArgs>(handler, unregisterHandler).Handler;
      }

    #endregion
    }
  }